﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2022-2022 LiLong                                              **
**  This file is part of BluetoothCpp.                                          **
**                                                                              **
**  BluetoothCpp is free software: you can redistribute it and/or modify        **
**  it under the terms of the GNU Lesser General Public License as published by **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  BluetoothCpp is distributed in the hope that it will be useful,             **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU Lesser General Public License for more details.                         **
**                                                                              **
**  You should have received a copy of the GNU Lesser General Public License    **
**  along with BluetoothCpp.  If not, see <https://www.gnu.org/licenses/>.      **
**********************************************************************************/
#include "../Device.h"

#ifdef WIN32
#include <WinSock2.h>
#include <bluetoothapis.h>
#include <ws2bth.h>

#include "ErrorHandleWin.h"
#include "Utils.h"

namespace BTH
{
static constexpr auto epochDiff = 116444736000000000;
static constexpr auto rateDiff  = 10000;

inline std::chrono::time_point<std::chrono::system_clock> toStdTimePoint(const SYSTEMTIME& t)
{
    FILETIME ft;
    SystemTimeToFileTime(&t, &ft);
    auto uLarge = reinterpret_cast<ULARGE_INTEGER*>(&ft);
    auto ms     = (uLarge->QuadPart - epochDiff) / rateDiff;
    return std::chrono::time_point<std::chrono::system_clock>(std::chrono::milliseconds(ms));
}

std::vector<Device> Device::findDevices()
{
    auto deleter    = [](void* p) { free(p); };
    DWORD querySize = sizeof(WSAQUERYSETW);

    std::unique_ptr<WSAQUERYSETW, decltype(deleter)> query((WSAQUERYSETW*)malloc(querySize), deleter);

    ZeroMemory(query.get(), sizeof(WSAQUERYSETW));
    query->dwSize      = sizeof(WSAQUERYSETW);
    query->dwNameSpace = NS_BTH;

    HANDLE handle;
    auto control = LUP_FLUSHCACHE | LUP_RETURN_TYPE | LUP_RES_SERVICE | LUP_RETURN_NAME | LUP_RETURN_ADDR;
    auto ret     = WSALookupServiceBeginW(query.get(), LUP_CONTAINERS | control, &handle);
    if (ret == SOCKET_ERROR)
        throwError(WSAGetLastError());
    std::shared_ptr<char> scope { nullptr, [&](char* p) { WSALookupServiceEnd(handle); } };
    DWORD size = querySize;
    std::vector<Device> devices;
    for (bool lookup = true; lookup;)
    {
        ret = WSALookupServiceNextW(handle, control, &size, query.get());
        if (ret == SOCKET_ERROR)
        {
            auto error = WSAGetLastError();
            switch (error)
            {
            case WSA_E_NO_MORE:
                lookup = false;
                break;
            case WSAEFAULT:
                query = std::unique_ptr<WSAQUERYSETW, decltype(deleter)>((WSAQUERYSETW*)malloc(size), deleter);
                break;
            default:
                throwError(error);
            }
        }
        else
        {
            SOCKADDR_BTH addr;
            memcpy(&addr, query->lpcsaBuffer->RemoteAddr.lpSockaddr, sizeof(addr));

            BLUETOOTH_DEVICE_INFO deviceInfo;
            deviceInfo.dwSize          = sizeof(BLUETOOTH_DEVICE_INFO);
            deviceInfo.Address.ullLong = addr.btAddr;
            deviceInfo.ulClassofDevice = 0;
            deviceInfo.fConnected      = false;
            deviceInfo.fRemembered     = false;
            deviceInfo.fAuthenticated  = false;
            if (auto ret = BluetoothGetDeviceInfo(nullptr, &deviceInfo); ret != ERROR_SUCCESS)
            {
                throwError(ret);
            }
            devices.push_back(Device(deviceInfo));
        }
    }
    return devices;
}

Device::Device(const _BLUETOOTH_DEVICE_INFO& info) : Device()
{
    setName(fromWChart(info.szName, std::wcslen(info.szName)));
    setAddress(info.Address.ullLong);
    setConnected(info.fConnected);
    setRemembered(info.fRemembered);
    setAuthenticated(info.fAuthenticated);

    // baseband.pdf
    setMajorDeviceClass(static_cast<Device::MajorDeviceClass>((info.ulClassofDevice & 0x1F00) >> 8));
    setMinorDeviceClass((info.ulClassofDevice & 0xFC) >> 2);
    setServiceClass(static_cast<Device::ServiceClass>((info.ulClassofDevice & 0xFFE000) >> 13));

    if (info.stLastSeen.wYear > 1601)
        setLastSeenTime(toStdTimePoint(info.stLastSeen));
    if (info.stLastUsed.wYear > 1601)
        setLastUsedTime(toStdTimePoint(info.stLastUsed));
}
} // namespace BTH
#endif
